MemoryCache 在 ASP.NET MVC 上的應用
TLDR
OutputCacheAttribute是 MVC 內建的快取機制,預設使用MemoryCache實作。- 若需針對不同使用者或權限進行快取,需透過
Global.asax.cs的GetVaryByCustomString實作自訂快取 Key。 NoStore與Location.None的行為不同:NoStore僅影響瀏覽器快取,Location.None則同時停用伺服器與瀏覽器快取。- 若需確保資料庫異動時快取能即時更新,可使用
SqlChangeMonitor結合SqlDependency監聽資料庫變更。 - 使用
SqlDependency前,必須在 SQL Server 資料庫中啟用Service Broker功能。
使用 ActionFilter 快取 Action 內容
OutputCacheAttribute 是 ASP.NET MVC 提供的 ActionFilter,用於標註 Action Method 的快取行為。
核心屬性說明
- Duration: 快取持續時間(秒)。
- Location: 指定快取儲存位置(如
Server,Client,Any等)。 - VaryByParam: 根據參數(如 QueryString 或 POST 參數)區分快取內容。
- CacheProfile: 引用
Web.config中預定義的快取設定,便於統一管理。
TIP
NoStore 與 Location.None 的差異:
NoStore: 將Cache-Control設為no-store,僅告知瀏覽器不要快取,不影響 Web Server 的快取行為。Location.None: 將Cache-Control設為no-cache,並強制不儲存 Web Server 的快取。
針對不同使用者進行快取
當應用程式需要根據使用者權限或 Cookie 區分快取內容時,單純使用 VaryByParam 會導致快取衝突。此時需在 Global.asax.cs 中覆寫 GetVaryByCustomString 方法。
什麼情況下會遇到這個問題:當網站具有使用者登入機制,且不同權限的使用者存取相同 URL 時,若未區分快取 Key,會導致使用者看到他人的快取資料。
實作範例
在 Global.asax.cs 中定義自訂邏輯:
csharp
public override string GetVaryByCustomString(HttpContext context, string custom) {
const string OutputCacheKey = "OutputCacheId";
if (custom.Equals("Cookie", StringComparison.OrdinalIgnoreCase)) {
if (Request.Cookies[OutputCacheKey] == null) {
string cacheId = Guid.NewGuid().ToString();
Response.Cookies.Add(new HttpCookie(OutputCacheKey) {
Value = cacheId,
HttpOnly = true,
Expires = DateTime.Now.AddHours(1)
});
return cacheId;
}
return Request.Cookies[OutputCacheKey].Value;
}
return base.GetVaryByCustomString(context, custom);
}更新資料庫時清除快取資料
為了避免資料庫異動後,快取仍保留舊資料,可以使用 SqlChangeMonitor 監聽 SQL Server 的變更。
什麼情況下會遇到這個問題:當快取資料來源於資料庫,且該資料庫資料會被其他外部程式或直接透過 SQL 指令修改,導致應用程式無法透過 API 主動清除快取時。
實作機制
SqlChangeMonitor 透過 SqlDependency 監聽 SQL Server 的通知。當資料異動時,SqlDependency 會觸發事件,進而通知 MemoryCache 清除對應的快取項目。
實作步驟
- 啟用 Service Broker: 必須在資料庫執行
ALTER DATABASE {資料庫名稱} SET ENABLE_BROKER;。 - 初始化監聽: 在
Global.asax.cs的Application_Start呼叫SqlDependency.Start()。 - 設定監控: 在建立快取時,將
SqlChangeMonitor加入CacheItemPolicy。
監聽範例
csharp
private void CreateCache() {
string connectionStr = WebConfigurationManager.ConnectionStrings["MyDB"].ConnectionString;
CacheItemPolicy policy = new CacheItemPolicy();
using (SqlConnection conn = new SqlConnection(connectionStr))
using (SqlCommand cmd = new SqlCommand("SELECT Key1 FROM dbo.Config", conn)) {
SqlDependency dependency = new SqlDependency(cmd);
dependency.OnChange += SqlDependencyOnChange;
conn.Open();
string key1 = cmd.ExecuteScalar().ToString();
SqlChangeMonitor monitor = new SqlChangeMonitor(dependency);
policy.ChangeMonitors.Add(monitor);
MemoryCache.Default.Set(CacheKey, key1, policy);
}
}WARNING
- 監聽的 SQL 語法必須包含 Schema(例如
dbo.TableName),且必須指定具體欄位。 SqlDependency設定後,必須執行一次SqlCommand監聽才會生效。- 若資料庫曾卸載重掛,啟用 Service Broker 可能需使用
ALTER DATABASE {資料庫名稱} SET NEW_BROKER WITH ROLLBACK IMMEDIATE;。
異動歷程
- 初版文件建立。